#include "gui_image.h"
#include <uview.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
// image lib
#include <png.h>
#include <jpeglib.h>
#include <jmorecfg.h>
#include <jconfig.h>

static uint8_t *__gui_load_png_image(const char *file, int *width, int *height, int *channel);
static uint8_t *__gui_load_jpg_image(const char *file, int *width, int *height, int *channel);

static int __gui_check_png_image(FILE **fp, const char *file)
{
    uint8_t checkheader[PNG_BYTES_TO_CHECK];
    *fp = fopen(file, "rb");
    if (!*fp)
    {
        dprintf("%s: open file %s failed\n", __func__, file);
        return -1;
    }
    uint32_t res = 0;
    if ((res = fread(checkheader, 1, PNG_BYTES_TO_CHECK, *fp)) != PNG_BYTES_TO_CHECK)
    {
        dprintf("%s: read %s header failed! res %d\n", __func__, file, res);
        return -1;
    }
    fclose(*fp);
    return png_sig_cmp((png_const_bytep)checkheader, 0, PNG_BYTES_TO_CHECK);
}

static int __gui_decode_png_image(const char *file, gui_image_png_t *out)
{
    png_structp png_ptr; // png file
    png_infop info_ptr;  // png image info
    FILE *fp = NULL;

    // check png header
    if (__gui_check_png_image(&fp, file))
    {
        dprintf("%s: file %s isn't png image!\n", __func__, file);
        return -1;
    }
    // init png struct
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    info_ptr = png_create_info_struct(png_ptr);

    if (!png_ptr || !info_ptr)
    {
        dprintf("[gui_image] create png struct err\n");
        return -1;
    }
    // set error
    setjmp(png_jmpbuf(png_ptr));

    //reopen file
    fp=fopen(file,"rb");
    if(!fp)
        return -1;
        
    // bind png info struct and file stream io
    png_init_io(png_ptr, fp);
   
    // read png file info
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);

    int channels, color_type;
    channels = png_get_channels(png_ptr, info_ptr);        // channels
    color_type = png_get_color_type(png_ptr, info_ptr);    // color type
    out->bit_depth = png_get_bit_depth(png_ptr, info_ptr); // bit depth
    out->width = png_get_image_width(png_ptr, info_ptr);   // image width
    out->height = png_get_image_height(png_ptr, info_ptr); // image height
    out->channels = channels;

    dprintf("[png] channel %d color type %d bits %d width %d height %d\n", channels, color_type, out->bit_depth, out->width, out->height);

    // read rgb data
    int i, j, k;
    int size, pos = 0;
    int temp;

    png_bytepp row_pointers; // actual store rgb data
    row_pointers = png_get_rows(png_ptr, info_ptr);
    size = out->width * out->height;
    if (channels == 4 || color_type == PNG_COLOR_TYPE_RGB_ALPHA)
    {
        out->alpha_flags = HAVE_ALPHA; // record has alpha channel
        size *= 4;
        out->rgba = (png_bytep)malloc(size); // alloc buffer
        if (!out->rgba)
        {
            dprintf("%s: malloc rgb buffer for %s failed!\n", __func__, file);
            png_destroy_read_struct(&png_ptr, &info_ptr, 0);
            fclose(fp);
            return -1;
        }
        // read data from row points
        temp = channels - 1;
        for (i = 0; i < out->height; i++)
        {
            for (j = 0; j < out->width * 4; j += 4)
            {
                for (k = temp; k >= 0; k--)
                {
                    out->rgba[pos++] = row_pointers[i][j + k];
                }
            }
        }
    }
    else
    {
        if (channels == 3 || color_type == PNG_COLOR_TYPE_RGB)
        {
            out->alpha_flags = NOT_HAVE_ALPHA;
            size *= 3;
            out->rgba = malloc(size);
            if (!out->rgba)
            {
                dprintf("%s: malloc rgb buffer for %s failed!\n", __func__, file);
                png_destroy_read_struct(&png_ptr, &info_ptr, 0);
                fclose(fp);
                return -1;
            }
            // read data from row points
            temp = 3 * out->width;
            for (i = 0; i < out->height; i++)
            {
                for (j = 0; j < temp; j += 3)
                {
                    out->rgba[pos++] = row_pointers[i][j + 2];
                    out->rgba[pos++] = row_pointers[i][j + 1];
                    out->rgba[pos++] = row_pointers[i][j + 0];
                }
            }
        }
        else
        {
            dprintf("%s: image %s png support channels %d type %x\n", __func__, channels, color_type);
            png_destroy_read_struct(&png_ptr, &info_ptr, 0);
            fclose(fp);
            return -1;
        }
    }
    // destroy memory
    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    fclose(fp);
    return 0;
}

static uint8_t *__gui_load_png_image(const char *file, int *width, int *height, int *channel)
{
    gui_image_png_t out;
    if (__gui_decode_png_image(file, &out) < 0)
        return NULL;
    // expand to 4 bytes per pixels
    uint32_t *bitmap = malloc(out.width * out.height * 4);
    if (!bitmap)
    {
        free(out.rgba);
        return NULL;
    }
    uint8_t *p;
    uint32_t color;
    int x, y;
    for (y = 0; y < out.height; y++)
    {
        for (x = 0; x < out.width; x++)
        {
            uint8_t r, g, b, a = 255;
            if (out.alpha_flags & HAVE_ALPHA)
            {
                p = out.rgba + (y * out.width + x) * 4;
                a = *p++;
                b = *p++;
                g = *p++;
                r = *p++;
            }
            else
            {
                p = out.rgba + (y * out.width + x) * 3;
                b = *p++;
                g = *p++;
                r = *p++;
            }
            color = (a << 24) | (r << 16) | (g << 8) | (b);
            bitmap[y * out.width + x] = color;
        }
    }
    free(out.rgba);
    if (width)
        *width = out.width;
    if (height)
        *height = out.height;
    if (channel)
        *channel = out.channels;
    return (uint8_t *)bitmap;
}

static uint8_t *__gui_load_jpg_image(const char *file, int *width, int *height, int *channel)
{
    // alloc jpeg decommpress object
    struct jpeg_decompress_struct dinfo;
    struct jpeg_error_mgr jerr;
    //set err manager
    dinfo.err=jpeg_std_error(&jerr);

    jpeg_create_decompress(&dinfo); // init decompress object

    // set need to compressed object
    FILE *infile;
    infile = fopen(file, "r");
    if (!infile)
    {
        printf("%s: open file %s failed!\n", __func__, file);
        fclose(infile);
        return NULL;
    }

    // set decompress object
    jpeg_stdio_src(&dinfo, infile); 

    // get image info
    if (jpeg_read_header(&dinfo, TRUE) != JPEG_HEADER_OK)
    {
        printf("%s: read %s header failed!\n", __func__, file);
        fclose(infile);
        return NULL;
    }
    // start decompress
    jpeg_start_decompress(&dinfo);
    //alloc scanline bufffer
    uint8_t *buffer = malloc(dinfo.output_width * dinfo.output_components);
    if (!buffer)
    {
        printf("%s: malloc buffer for %s failed!\n", __func__, file);
        fclose(infile);
        jpeg_destroy_decompress(&dinfo);
        return NULL;
    }

    if (width)
        *width = dinfo.output_width;
    if (height)
        *height = dinfo.output_height;
    if (channel)
        *channel = dinfo.output_components;
    
    dprintf("[gui_image] jpg image width %d height %d channel %d\n",*width,*height,*channel);

    /// read data
    uint32_t *bitmap = malloc(dinfo.output_width * dinfo.output_height * 4);
    if (!bitmap)
    {
        printf("%s: malloc for %s failed!\n", __func__, file);
        free(buffer);
        fclose(infile);
        jpeg_destroy_decompress(&dinfo);
        return NULL;
    }

    uint32_t *pixel = bitmap;
    uint32_t pixelidx = 0;
    while (dinfo.output_scanline < dinfo.output_height)
    {
        uint8_t *buff_temp[1];
        buff_temp[0] = buffer;

        jpeg_read_scanlines(&dinfo, buff_temp, 1);

        int x;
        uint8_t *p = buff_temp[0];
        for (x = 0; x < dinfo.output_width; x++)
        {
            uint8_t r, g, b, a = 255;
            uint32_t color;
            if (dinfo.output_components == 3)
            {
                r = *p++;
                g = *p++;
                b = *p++;
            }
            else
            {
                a = *p++;
                r = *p++;
                g = *p++;
                b = *p++;
            }
            color = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (b);
            pixel[pixelidx] = color;
            pixelidx++;
        }
    }

    // finish decompress
    jpeg_finish_decompress(&dinfo);
    // jpeg decompress destroy
    jpeg_destroy_decompress(&dinfo);
    free(buffer);
    fclose(infile);
    dprintf("[gui_image] jpge img load finish\n");
    return (uint8_t *)bitmap;
}

uint8_t *gui_load_image_raw(const char *file, int *width, int *height, int *channel)
{
    char *type = strrchr(file, '.');
    if (!type)
    {
        #ifdef DEBUG_IMAGE
        dprintf("[gui_image[ %s: file %s unrecognized image format!\n", __func__, file);
        #endif
        return NULL;
    }
    type++; // skip '.'
    if (!type)
    {
        #ifdef DEBUG_IMAGE
        dprintf("[gui_image] %s: file %s unrecognized image format!\n", __func__, file);
        #endif 
        return NULL;
    }
    dprintf("[gui_img] img type: %s\n", type);

    uint8_t *image = NULL;
    if (!strcmp(type, "png"))
        image = __gui_load_png_image(file, width, height, channel);
    else if (!strcmp(type, "jpg"))
        image = __gui_load_jpg_image(file, width, height, channel);
    if (!image)
    {
        dprintf("[gui_image] %s: load iamge failed!\n", __func__);
        return NULL;
    }
    return image;
}

static void __gui_resize_image_mode(uint8_t *src_buff, int src_w, int src_h, uint8_t *dst_buff, int dst_w, int dst_h, int num_channels, gui_image_stretch_mode_t mode)
{
    int bitcount = num_channels * 8;

    // source image cache
    int src_linesz = bitcount * src_w / 8;
    int dst_linesz = ((dst_w * bitcount + 31) / 32) * 4;
    double rate_h = (double)src_h / dst_h;
    double rate_w = (double)src_w / dst_w;
    int i, j, k;

    if (mode == GRSZ_NEAREST)
    {
        for (i = 0; i < dst_h; i++)
        {
            int tsrc_h = (int)(rate_h * i + 0.5);
            for (j = 0; j < dst_w; j++)
            {
                int tsrc_w = (int)(rate_w * j + 0.5);
                memcpy(&dst_buff[i * dst_linesz] + j * bitcount / 8, &src_buff[tsrc_h * src_linesz] + tsrc_w * bitcount / 8, bitcount / 8);
            }
        }
    }
    else
    {
        for (i = 0; i < dst_h; i++)
        {
            int th = (rate_h * i);
            int thl = min(th + 1, src_h - 1);
            float u = (float)(rate_h * i - th);
            for (j = 0; j < dst_w; j++)
            {
                int tw = (int)(rate_w * j);
                int twl = min(tw + 1, src_w - 1);
                float v = (float)(rate_w * j - tw);

                for (k = 0; k < 4; k++)
                {
                    dst_buff[i * dst_linesz + j * bitcount / 8 + k] = (1 - u) * (1 - v) * src_buff[th * src_linesz + tw * bitcount / 8 + k] +
                                                                      (1 - u) * v * src_buff[thl * src_linesz + tw * bitcount / 8 + k] +
                                                                      u * (1 - v) * src_buff[th * src_linesz + twl * bitcount / 8 + k] +
                                                                      u * v * src_buff[thl * src_linesz + twl * bitcount / 8 + k];
                }
            }
        }
    }
}

void gui_resize_image_raw(uint8_t *src_buff, int src_w, int src_h, uint8_t *dst_buff, int dst_w, int dst_h, int num_channels)
{
    __gui_resize_image_mode(src_buff, src_w, src_h, dst_buff, dst_w, dst_h, num_channels, GRSZ_BILINEAR);
}

gui_image_t *gui_image_load(char *file)
{
    gui_image_t *img = malloc(sizeof(gui_image_t));
    if (!img)
        return NULL;
    img->buff = gui_load_image_raw(file, &img->w, &img->h, &img->channel);
    if (!img->buff)
    {
        free(img);
        return NULL;
    }
    return img;
}

void gui_image_destroy(gui_image_t *img)
{
    if (!img)
        return -1;
    if (img->buff)
        free(img->buff);
    free(img);
    return 0;
}

int gui_image_resize(gui_image_t *img, int w, int h)
{
    if (!img)
        return -1;
    uint8_t *buff = malloc(w * h * img->channel);
    if (!buff)
        return -1;
    gui_resize_image_raw(img->buff, img->w, img->h, buff, w, h, img->channel);
    free(img->buff); //remove old image buffer data
    img->buff = buff;
    img->w = w;
    img->h = h;
    return 0;
}

int gui_image_resize2(gui_image_t *img, int w, int h, int out_chanels)
{
    if (!img)
        return -1;
    uint8_t *buff = malloc(w * h * out_chanels);
    if (!buff)
        return -1;
    gui_resize_image_raw(img->buff, img->w, img->h, buff, w, h, out_chanels);
    free(img->buff);
    img->buff = buff;
    img->w = w;
    img->h = h;
    return 0;
}

gui_image_t *gui_image_load2(char *file, int w, int h)
{
    gui_image_t *img = gui_image_load(file);
    if (!img)
        return NULL;
    if (gui_image_resize(img, w, h) < 0)
    {
        gui_image_destroy(img);
        return NULL;
    }
    return img;
}
